package classfile

import (
	"gitee.com/microwww/jvm-go/jvm/tool"
	"encoding/binary"
	"errors"
	"fmt"
)

// jvm 官方 se8 的虚拟机规范
// https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
type ClassFile struct {
	magic               uint32 // 0xCAFEBABE
	minor_version       uint16
	major_version       uint16
	constant_pool_count uint16
	cp_info             ConstantPool // cp_info        cp_info[constant_pool_count-1];
	access_flags        uint16
	this_class          uint16
	super_class         uint16
	interfaces_count    uint16
	inter_info          []InterfaceInfo
	fields_count        uint16
	field_info          []FieldInfo
	methods_count       uint16
	method_info         []MethodInfo
	attributes_count    uint16
	attribute_info      []AttributeInfo
}

func utf8m(cp ConstantPool, index uint16) string {
	return cp[index].(*CONSTANT_Utf8_info).Utf8m()
}

func (c *ClassFile) MethodInfo() []MethodInfo {
	return c.method_info
}

func (c *ClassFile) AccessFlags() uint16 {
	return c.access_flags
}

func (c *ClassFile) SuperClass() string { // java.lang.Object == nil
	if c.super_class == 0 {
		return ""
	}
	return c.cp_info[c.super_class].(*CONSTANT_Class_info).Name()
}

func (c *ClassFile) ThisClass() string {
	return c.cp_info[c.this_class].(*CONSTANT_Class_info).Name()
}

func (c *ClassFile) InterfaceInfo() []InterfaceInfo {
	return c.inter_info
}

func (c *ClassFile) FieldInfo() []FieldInfo {
	return c.field_info
}

func (c *ClassFile) ConstantPool() ConstantPool {
	return c.cp_info
}

const (
	CONSTANT_Class              = 7
	CONSTANT_Fieldref           = 9
	CONSTANT_Methodref          = 10
	CONSTANT_InterfaceMethodref = 11
	CONSTANT_String             = 8
	CONSTANT_Integer            = 3
	CONSTANT_Float              = 4
	CONSTANT_Long               = 5
	CONSTANT_Double             = 6
	CONSTANT_NameAndType        = 12
	CONSTANT_Utf8               = 1
	CONSTANT_MethodHandle       = 15
	CONSTANT_MethodType         = 16
	CONSTANT_InvokeDynamic      = 18
)

type ConstantInfo interface {
	Read(*tool.Buffer)
}

type ConstantPool []ConstantInfo

func (c *ConstantPool) GetConstantInfo(index uint16) ConstantInfo {
	return (*c)[index]
}

type CONSTANT_Class_info struct {
	cp         ConstantPool
	name_index uint16
}

func (c *CONSTANT_Class_info) Read(bf *tool.Buffer) {
	c.name_index = binary.BigEndian.Uint16(bf.Next(2))
}

func (c *CONSTANT_Class_info) Name() string {
	return utf8m(c.cp, c.name_index)
}

type CONSTANT_Fieldref_info struct {
	cp                  ConstantPool
	class_index         uint16
	name_and_type_index uint16
}

func (c *CONSTANT_Fieldref_info) ClassName() string {
	ci := c.cp[c.class_index].(*CONSTANT_Class_info)
	return utf8m(c.cp, ci.name_index)
}

func (c *CONSTANT_Fieldref_info) NameAndType() (string, string) {
	info := c.cp[c.name_and_type_index].(*CONSTANT_NameAndType_info)
	return info.Name(), info.Descriptor()
}

func (c *CONSTANT_Fieldref_info) Read(bf *tool.Buffer) {
	c.class_index = binary.BigEndian.Uint16(bf.Next(2))
	c.name_and_type_index = binary.BigEndian.Uint16(bf.Next(2))
}

type CONSTANT_Methodref_info struct {
	CONSTANT_Fieldref_info
}

type CONSTANT_InterfaceMethodref_info struct {
	CONSTANT_Fieldref_info
}

type CONSTANT_String_info struct {
	cp           ConstantPool
	string_index uint16
}

func (c *CONSTANT_String_info) Read(bf *tool.Buffer) {
	c.string_index = binary.BigEndian.Uint16(bf.Next(2))
}

func (c *CONSTANT_String_info) StringValue() string {
	return utf8m(c.cp, c.string_index)
}

type CONSTANT_Integer_info struct {
	val int32
}

func (c *CONSTANT_Integer_info) Value() int32 {
	return c.val
}

func (c *CONSTANT_Integer_info) Read(bf *tool.Buffer) {
	c.val = int32(binary.BigEndian.Uint32(bf.Next(4)))
}

// int s = ((bits >> 31) == 0) ? 1 : -1;
// int e = ((bits >> 23) & 0xff);
// int m = (e == 0) ?
//          (bits & 0x7fffff) << 1 :
//          (bits & 0x7fffff) | 0x800000;
type CONSTANT_Float_info struct {
	val float32
}

func (c *CONSTANT_Float_info) Value() float32 {
	return c.val
}

func (c *CONSTANT_Float_info) Read(bf *tool.Buffer) {
	c.val = float32(binary.BigEndian.Uint32(bf.Next(4)))
}

type CONSTANT_Long_info struct {
	val int64
}

func (c *CONSTANT_Long_info) Value() int64 {
	return c.val
}

func (c *CONSTANT_Long_info) Read(bf *tool.Buffer) {
	c.val = int64(binary.BigEndian.Uint64(bf.Next(8)))
}

type CONSTANT_Double_info struct {
	val float64
}

func (c *CONSTANT_Double_info) Value() float64 {
	return c.val
}

func (c *CONSTANT_Double_info) Read(bf *tool.Buffer) {
	c.val = float64(binary.BigEndian.Uint64(bf.Next(8)))
}

type CONSTANT_NameAndType_info struct {
	cp               ConstantPool
	name_index       uint16
	descriptor_index uint16
}

func (c *CONSTANT_NameAndType_info) Read(bf *tool.Buffer) {
	c.name_index = binary.BigEndian.Uint16(bf.Next(2))
	c.descriptor_index = binary.BigEndian.Uint16(bf.Next(2))
}

func (c *CONSTANT_NameAndType_info) Name() string {
	return utf8m(c.cp, c.name_index)
}

func (c *CONSTANT_NameAndType_info) Descriptor() string {
	return utf8m(c.cp, c.descriptor_index)
}

type CONSTANT_Utf8_info struct {
	length uint16
	bytes  []byte
}

func (c *CONSTANT_Utf8_info) Utf8m() string {
	return string(c.bytes)
}

func (c *CONSTANT_Utf8_info) Read(bf *tool.Buffer) {
	c.length = binary.BigEndian.Uint16(bf.Next(2))
	c.bytes = bf.Next(int(c.length))
}

type CONSTANT_MethodHandle_info struct {
	cp              ConstantPool
	reference_kind  uint8
	reference_index uint16
}

func (c *CONSTANT_MethodHandle_info) Read(bf *tool.Buffer) {
	c.reference_kind, _ = bf.ReadByte()
	c.reference_index = binary.BigEndian.Uint16(bf.Next(2))
}

type CONSTANT_MethodType_info struct {
	cp               ConstantPool
	descriptor_index uint16
}

func (c *CONSTANT_MethodType_info) Read(bf *tool.Buffer) {
	c.descriptor_index = binary.BigEndian.Uint16(bf.Next(2))
}

type CONSTANT_InvokeDynamic_info struct {
	cp                          ConstantPool
	bootstrap_method_attr_index uint16
	name_and_type_index         uint16
}

func (c *CONSTANT_InvokeDynamic_info) Read(bf *tool.Buffer) {
	c.bootstrap_method_attr_index = binary.BigEndian.Uint16(bf.Next(2))
	c.name_and_type_index = binary.BigEndian.Uint16(bf.Next(2))
}

type StringInfo struct {
	cp         ConstantPool
	name_index uint16
}

func (s *StringInfo) Name() string {
	return utf8m(s.cp, s.name_index)
}

func (c *StringInfo) Read(bf *tool.Buffer) {
	c.name_index = binary.BigEndian.Uint16(bf.Next(2))
}

type InterfaceInfo = ClassInfo

type ClassInfo struct {
	cp ConstantPool
	StringInfo
}

func (c *ClassInfo) Name() string {
	return c.cp[c.name_index].(*CONSTANT_Class_info).Name()
}

type FieldInfo struct {
	cp               ConstantPool
	access_flags     uint16
	name_index       uint16
	descriptor_index uint16
	attributes_count uint16
	attributes       []AttributeInfo
}

func (c *FieldInfo) readWithAttributes(bf *tool.Buffer, pool ConstantPool) {
	c.Read(bf)
	info := make([]AttributeInfo, c.attributes_count)
	for i := uint16(0); i < c.attributes_count; i++ {
		attr := ReadAttribute(bf, pool)
		info[i] = attr
	}
}

func (c *FieldInfo) Read(bf *tool.Buffer) {
	c.access_flags = binary.BigEndian.Uint16(bf.Next(2))
	c.name_index = binary.BigEndian.Uint16(bf.Next(2))
	c.descriptor_index = binary.BigEndian.Uint16(bf.Next(2))
	c.attributes_count = binary.BigEndian.Uint16(bf.Next(2))
}

func (c *FieldInfo) AccessFlags() uint16 {
	return c.access_flags
}

func (c *FieldInfo) Name() string {
	return utf8m(c.cp, c.name_index)
}

func (c *FieldInfo) Descriptor() string {
	return utf8m(c.cp, c.descriptor_index)
}

type MethodInfo struct {
	cp               ConstantPool
	access_flags     uint16
	name_index       uint16
	descriptor_index uint16 // 方法参数
	attributes_count uint16
	attributes       []AttributeInfo
}

func (m *MethodInfo) Name() string {
	return utf8m(m.cp, m.name_index)
}

func (m *MethodInfo) AccessFlags() uint16 {
	return m.access_flags
}

func (m *MethodInfo) Descriptor() string {
	return utf8m(m.cp, m.descriptor_index)
}

func (m *MethodInfo) Attributes() []AttributeInfo {
	return m.attributes
}

// CodeAttribute : native will nil
func (m *MethodInfo) CodeAttribute() *CodeAttribute {
	for _, attr := range m.attributes {
		switch attr.(type) {
		case *CodeAttribute:
			return attr.(*CodeAttribute)
		}
	}
	return nil
}

type AttributeInfo interface {
	ReadAttribute(bf *tool.Buffer)
	Name() string
}

func ParseClassFile(bts []byte) *ClassFile {
	var bf = tool.NewBuffer(bts)

	cf := ClassFile{}
	cf.magic = binary.LittleEndian.Uint32(bf.Next(4))
	cf.minor_version = binary.BigEndian.Uint16(bf.Next(2))
	cf.major_version = binary.BigEndian.Uint16(bf.Next(2))
	cf.constant_pool_count = binary.BigEndian.Uint16(bf.Next(2))

	po := make(ConstantPool, cf.constant_pool_count)

	// first is nil
	for i := uint16(1); i < cf.constant_pool_count; i++ {
		tag, _ := bf.ReadByte()
		switch tag {
		case CONSTANT_Class:
			v := &CONSTANT_Class_info{}
			v.Read(bf)
			v.cp = po
			po[i] = v
		case CONSTANT_Fieldref:
			v := &CONSTANT_Fieldref_info{}
			v.Read(bf)
			v.cp = po
			po[i] = v
		case CONSTANT_Methodref:
			v := &CONSTANT_Methodref_info{}
			v.Read(bf)
			v.cp = po
			po[i] = v
		case CONSTANT_InterfaceMethodref:
			v := &CONSTANT_InterfaceMethodref_info{}
			v.Read(bf)
			v.cp = po
			po[i] = v
		case CONSTANT_String:
			v := &CONSTANT_String_info{}
			v.Read(bf)
			v.cp = po
			po[i] = v
		case CONSTANT_Integer:
			v := &CONSTANT_Integer_info{}
			v.Read(bf)
			po[i] = v
		case CONSTANT_Float:
			v := &CONSTANT_Float_info{}
			v.Read(bf)
			po[i] = v
		case CONSTANT_Long:
			v := &CONSTANT_Long_info{}
			v.Read(bf)
			po[i] = v
			// 多增加一个
			i++
		case CONSTANT_Double:
			v := &CONSTANT_Double_info{}
			v.Read(bf)
			po[i] = v
			// 多增加一个
			i++
		case CONSTANT_NameAndType:
			v := &CONSTANT_NameAndType_info{}
			v.Read(bf)
			v.cp = po
			po[i] = v
		case CONSTANT_Utf8:
			v := &CONSTANT_Utf8_info{}
			v.Read(bf)
			po[i] = v
		case CONSTANT_MethodHandle:
			v := &CONSTANT_MethodHandle_info{}
			v.Read(bf)
			v.cp = po
			po[i] = v
		case CONSTANT_MethodType:
			v := &CONSTANT_MethodType_info{}
			v.Read(bf)
			v.cp = po
			po[i] = v
		case CONSTANT_InvokeDynamic:
			v := &CONSTANT_InvokeDynamic_info{}
			v.Read(bf)
			v.cp = po
			po[i] = v
		default:
			fmt.Printf("count : `%v` \n", i)
			panic(errors.New(fmt.Sprintf("Not support ConstantPool type : `%x`, index : %v", tag, i)))
		}
	}

	cf.cp_info = po
	cf.access_flags = binary.BigEndian.Uint16(bf.Next(2))
	cf.this_class = binary.BigEndian.Uint16(bf.Next(2))
	cf.super_class = binary.BigEndian.Uint16(bf.Next(2))
	cf.interfaces_count = binary.BigEndian.Uint16(bf.Next(2))
	iif := make([]InterfaceInfo, cf.interfaces_count)
	for i := uint16(0); i < cf.interfaces_count; i++ {
		v := InterfaceInfo{}
		v.cp = cf.cp_info
		v.Read(bf)
		iif[i] = v
	}
	cf.inter_info = iif

	cf.fields_count = binary.BigEndian.Uint16(bf.Next(2))
	fi := make([]FieldInfo, cf.fields_count)
	for i := uint16(0); i < cf.fields_count; i++ {
		f := FieldInfo{}
		f.cp = cf.cp_info
		f.readWithAttributes(bf, cf.cp_info)
		fi[i] = f
	}
	cf.field_info = fi

	cf.methods_count = binary.BigEndian.Uint16(bf.Next(2))
	mi := make([]MethodInfo, cf.methods_count)
	for i := uint16(0); i < cf.methods_count; i++ {
		m := MethodInfo{cp: cf.cp_info}
		m.access_flags = binary.BigEndian.Uint16(bf.Next(2))
		m.name_index = binary.BigEndian.Uint16(bf.Next(2))
		m.descriptor_index = binary.BigEndian.Uint16(bf.Next(2))
		m.attributes_count = binary.BigEndian.Uint16(bf.Next(2))

		// name := cf.cp_info[m.name_index].(*CONSTANT_Utf8_info).str
		// log.Default().Printf("Parse method `%v`", name)

		attrs := make([]AttributeInfo, m.attributes_count)
		for i := uint16(0); i < m.attributes_count; i++ {
			attrs[i] = ReadAttribute(bf, cf.cp_info)
		}
		// log.Default().Printf("Parse method attr count `%v`", m.attributes_count)

		m.attributes = attrs
		mi[i] = m
	}
	cf.method_info = mi

	cf.attributes_count = binary.BigEndian.Uint16(bf.Next(2))
	attrs := make([]AttributeInfo, cf.attributes_count)
	for i := uint16(0); i < cf.attributes_count; i++ {
		attrs[i] = ReadAttribute(bf, cf.cp_info)
	}
	cf.attribute_info = attrs

	return &cf
}
